package uk.org.brindy.osgi.fvis.bundle;

import java.util.ArrayList;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleEvent;
import org.osgi.framework.BundleListener;
import org.osgi.framework.Constants;

@SuppressWarnings("unchecked")
public class BundleToJSON {

	private static final String QUOTE = "\"";
	private static final String COLON = ":";
	private static final String NULL = "null";
	private static final String COMMA = ",";
	private static final String OPEN_OBJECT = "{";
	private static final String CLOSE_OBJECT = "}";
	private static final String OPEN_ARRAY = "[";
	private static final String CLOSE_ARRAY = "]";

	private final BundleContext context;

	private boolean dirty = true;

	private String json;

	public BundleToJSON(BundleContext context) {
		this.context = context;
		context.addBundleListener(new BundleListener() {
			public void bundleChanged(BundleEvent event) {
				dirty = true;
			}
		});
	}

	public boolean isDirty() {
		return dirty;
	}

	private synchronized String createJSON() {

		StringBuffer buffer = new StringBuffer();

		buffer.append(OPEN_ARRAY).append("\n");
		int count = 0;
		for (Bundle bundle : context.getBundles()) {
			if (count > 0) {
				buffer.append(COMMA);
				buffer.append("\n");
			}
			buffer.append(OPEN_OBJECT);
			appendBundle(bundle, buffer);
			buffer.append(CLOSE_OBJECT);
			count++;
		}
		buffer.append("\n");
		buffer.append(CLOSE_ARRAY);
		return buffer.toString();
	}

	private void appendBundle(Bundle bundle, StringBuffer buffer) {
		Dictionary headers = bundle.getHeaders();

		addNamedHeader(Constants.BUNDLE_SYMBOLICNAME, headers, buffer);

		buffer.append(COMMA);
		addNamedHeader(Constants.BUNDLE_VERSION, headers, buffer);

		addListHeader(Constants.REQUIRE_BUNDLE, headers, buffer);
		addListHeader(Constants.IMPORT_PACKAGE, headers, buffer);
		addListHeader(Constants.EXPORT_PACKAGE, headers, buffer);
	}

	private void addNamedHeader(String name, Dictionary headers,
			StringBuffer buffer) {
		String value = (String) headers.get(name);
		String[] values = value.split(";");
		addNamedValue(name, values[0].trim(), buffer);
	}

	private void addNamedValue(String name, String value, StringBuffer buffer) {
		addName(name, buffer);
		if (null == value) {
			buffer.append(NULL);
		} else if (quoted(value)) {
			buffer.append(value);
		} else {
			buffer.append(QUOTE).append(value).append(QUOTE);
		}
	}

	private boolean quoted(String value) {
		return value.startsWith(QUOTE);
	}

	private void addListHeader(String name, Dictionary headers,
			StringBuffer buffer) {
		String listValue = (String) headers.get(name);
		if (listValue != null) {
			Map map = parseHeader(listValue);
			if (map.size() > 0) {
				buffer.append(COMMA);
				addName(name, buffer);
				buffer.append(OPEN_ARRAY);
				addValues(map, buffer);
				buffer.append(CLOSE_ARRAY);
			}
		}
	}

	public Map parseHeader(String value) {
		if (value == null || value.trim().length() == 0) {
			return new HashMap();
		}

		Map result = new HashMap();
		QuotedTokenizer qt = new QuotedTokenizer(value, ";=,");
		char del;
		do {
			boolean hadAttribute = false;
			Map clause = new HashMap();
			List aliases = new ArrayList();
			aliases.add(qt.nextToken());
			del = qt.getSeparator();
			while (del == ';') {
				String adname = qt.nextToken();
				if ((del = qt.getSeparator()) != '=') {
					if (hadAttribute) {
						throw new IllegalArgumentException(
								"Header contains name field after attribute or directive: "
										+ adname + " from " + value);
					}
					aliases.add(adname);
				} else {
					String advalue = qt.nextToken();
					clause.put(adname, advalue);
					del = qt.getSeparator();
					hadAttribute = true;
				}
			}
			for (Iterator i = aliases.iterator(); i.hasNext();) {
				result.put(i.next(), clause);
			}
		} while (del == ',');
		return result;
	}

	private void addValues(Map headerValues, StringBuffer buffer) {
		int count = 0;
		Iterator iter = headerValues.keySet().iterator();
		while (iter.hasNext()) {
			String value = (String) iter.next();
			if (count > 0) {
				buffer.append(COMMA);
			}
			buffer.append(OPEN_OBJECT);
			addNamedValue("name", value, buffer);
			buffer.append(CLOSE_OBJECT);
			count++;
		}
	}

	private void addName(String name, StringBuffer buffer) {
		buffer.append(QUOTE).append(name).append(QUOTE).append(COLON);
	}

	public String toString() {
		if (dirty) {
			synchronized (this) {
				json = createJSON();
			}
		}

		return json;
	}

}
